#include "ota_http.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <curl/curl.h> // sudo apt-get install libcurl4-openssl-dev
#include <openssl/sha.h> // sudo apt install libssl-dev
#include "cJSON/cJSON.h"
#include "log/log.h"

static size_t file_write_callback(void *ptr, size_t size, size_t nmemb, void *file)
{
    return fwrite(ptr, size, nmemb, (FILE *)file);
}

static int ota_http_downloadFile(const char *url, const char *filename)
{
    FILE *file = fopen(filename, "wb");
    if(!file)
    {
        log_error("Failed to open file: %s", filename);
        return -1;
    }
    int result = ota_http_curl(url, file_write_callback, file);
    fclose(file);
    if (result != 0)
    {
        log_error("Failed to download file: %s", url);
        unlink(filename);
    }

    return result;
}

static int str_write_callback(char *ptr, int size, int nmemb, void *str)
{
    int real_size = size * nmemb;
    if (real_size > strlen((char *)str))
    {
        real_size = strlen((char *)str);
    }
    memcpy(str, ptr, real_size);

    return real_size;
}

static int ota_http_curl(const char *url, curl_write_callback callback, void *data_p)
{
    
    CURLcode return_code;
    long http_return_code = 0;
    int result = 0;

    CURL *curl = curl_easy_init();
    if (!curl)
    {
        log_error("curl_easy_init() failed");
        return -1;
    }
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, data_p);

    return_code = curl_easy_perform(curl);
    if (return_code != CURLE_OK)
    {
        log_error("curl_easy_perform() failed: %s", curl_easy_strerror(return_code));
        result = -1;
    }
    else
    {
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_return_code);
        if (http_return_code != 200)
        {
            log_error("HTTP return code: %ld", http_return_code);
            result = -1;
        }
    }
    curl_easy_cleanup(curl);

    return result;
}

static int ota_http_getData(const char *url, char *data, int len)
{
    for (int i = 0; i < len-1; i++)
    {
        data[i] = 32;
    }
    data[len-1] = '\0';

    return ota_http_curl(url, str_write_callback, data);
}

int ota_http_getVersion(Version *verion)
{
    char response[4096];
    cJSON *cjson = NULL;
    ota_http_getData(VERSION_URL, response, sizeof(response));

    cjson = cJSON_Parse(response);
    if (cjson == NULL)
    {
        log_error("No valid version, recieved str: %s", cJSON_GetErrorPtr());
        return -1;
    }

    cJSON *major_cjson = cJSON_GetObjectItem(cjson, "major");
    cJSON *minor_cjson = cJSON_GetObjectItem(cjson, "minor");
    cJSON *patch_cjson = cJSON_GetObjectItem(cjson, "patch");
    if (!major_cjson || !minor_cjson || !patch_cjson)
    {
        log_error("No valid version");
        return -1;
    }

    verion->major = major_cjson->valueint;
    verion->minor = minor_cjson->valueint;
    verion->patch = patch_cjson->valueint;

    cJSON_Delete(cjson);
    
    return 0;
    
}

int ota_http_downloadFirmware(const char *filename)
{
    int result;
    char local_sha[SHA_DIGEST_LENGTH * 2 + 1];
    char remote_sha[SHA_DIGEST_LENGTH * 2 + 1];
    result = ota_http_getData(FIRMWARE_SHA_URL, remote_sha, SHA_DIGEST_LENGTH*2+1);
    if (result != 0)
    {
        log_error("Failed to get remote sha");
        return -1;
    }
    result = ota_http_downloadFile(FIRMWARE_URL, filename);
    if (result != 0)
    {
        log_error("Failed to download firmware");
        unlink(filename);
        return -1;
    }
    ota_http_calculateSHA(filename, local_sha);

    if (strncmp(local_sha, remote_sha, 2 * SHA_DIGEST_LENGTH) != 0)
    {
        log_error("SHA mismatch");
        unlink(filename);
        return -1;
    }

    return 0;
}